package net.qhzw.vip.qiyu.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import net.qhzw.vip.qiyu.domain.MailRemind;
import net.qhzw.vip.qiyu.dto.*;
import net.qhzw.vip.qiyu.entity.DtsUser;
import net.qhzw.vip.qiyu.entity.MaintRpt;
import net.qhzw.vip.qiyu.entity.MaintTasks;
import net.qhzw.vip.qiyu.entity.SysCommunity;
import net.qhzw.vip.qiyu.feign.CommunityFeignClient;
import net.qhzw.vip.qiyu.feign.DtsUserFeignClent;
import net.qhzw.vip.qiyu.handler.ApiException;
import net.qhzw.vip.qiyu.mapper.MaintRptMapper;
import net.qhzw.vip.qiyu.mapper.MaintTasksMapper;
import net.qhzw.vip.qiyu.model.BigDataInfo;
import net.qhzw.vip.qiyu.model.BigDataInfoFask;
import net.qhzw.vip.qiyu.param.AssignSiteParam;
import net.qhzw.vip.qiyu.param.MaintQuery;
import net.qhzw.vip.qiyu.result.PageResult;
import net.qhzw.vip.qiyu.service.MailService;
import net.qhzw.vip.qiyu.service.MaintRptService;
import net.qhzw.vip.qiyu.util.*;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.mail.MessagingException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class MaintRptServiceImpl extends ServiceImpl<MaintRptMapper, MaintRpt>
        implements MaintRptService {

    @Autowired
    private MaintRptMapper maintRptMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private CommunityFeignClient communityFeignClient;

    @Autowired
    private DtsUserFeignClent dtsUserFeignClent;

    @Autowired
    private MaintTasksMapper maintTasksMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MailService mailService;


    private Lock lock = new ReentrantLock();


    /**
     * 用户维修报案
     *
     * @param param
     * @return
     */
    @Override
    @Transactional
    public boolean report(MaintRpt param) {
        Long userId = SecurityUtils.getUserId();
        reportCheckd(userId);
        long id = IdWorker.getId();
        param.setId(id);
        param.setStatus((byte) 1);
        param.setUserId(userId);
        param.setCreated(new Date());
        save(param);
        //下发维修任务
        assignMaint(param);
        return true;
    }

    @Override
    public List<MaintRpt> maintQueueList(Long userId) {
        List<MaintRpt> list = null;
        SysCommunity community = maintRptMapper.findByDtsUserId(userId);
        if (ObjectUtils.isEmpty(community)) {
            return list;
        }
        List<String> maintQueue = redisTemplate.boundHashOps("maintQueue").values();
        if (!CollectionUtils.isEmpty(maintQueue)) {
            list = new ArrayList<>(maintQueue.size());
            for (String json : maintQueue) {
                MaintRpt maintRpt = JSONObject.parseObject(json, MaintRpt.class);
                double distance = CaculateDistance.LantitudeLongitudeDist(
                        community.getLongitude(), community.getLatitude(),
                        maintRpt.getLongitude(), maintRpt.getLatitude());
                distance = distance / 1000;
                maintRpt.setDistance(MyMathUtil.dFormat(distance));
                list.add(maintRpt);
            }
            Collections.sort(list, new Comparator<MaintRpt>() {
                @Override
                public int compare(MaintRpt o1, MaintRpt o2) {
                    double distance1 = CaculateDistance.LantitudeLongitudeDist(
                            community.getLongitude(), community.getLatitude(),
                            o1.getLongitude(), o1.getLatitude());
                    double distance2 = CaculateDistance.LantitudeLongitudeDist(
                            community.getLongitude(), community.getLatitude(),
                            o2.getLongitude(), o2.getLatitude());
                    int i = (int) distance1 - (int) distance2;
                    return i;
                }
            });
        }
        return list;
    }

    /**
     * 接单
     *
     * @param id
     * @param userId
     * @return
     */
    @Override
    public boolean takeAnOrdrNow(Long id, Long userId) {
        MaintRpt maintRpt = getById(id);
        checked(id);
        maintRpt.setStatus((byte) 3);
        maintRpt.setRepairUserId(userId);
        maintRpt.setDispatchTime(new Date());
        maintRpt.setOrderTakingTime(new Date());
        redisTemplate.boundHashOps("maintQueue").delete(maintRpt.getUserId());
        return updateById(maintRpt);
    }

    private void checked(Long id) {
        MaintRpt maintRpt = getById(id);
        if (ObjectUtils.isEmpty(id)) {
            throw new ApiException("网络异常, 订单不存在!");
        }
        if (maintRpt.getStatus().intValue() >= 3) {
            throw new ApiException("该订单已被其他人接单");
        }

    }

    /**
     * 无法接单
     *
     * @return
     */
    @Override
    @Transactional
    public boolean unableToTakeOrders(Long id, Long maintUserId) {
        MaintRpt maintRpt = getById(id);
        maintRpt.setStatus((byte) 1);
        maintRpt.setRepairUserId(0L);
        updateById(maintRpt);
        redisTemplate.boundHashOps("maintTasks").delete(maintUserId);
        maintTasksMapper.unableToTakeOrders(id);
        //存入维修队列
        //DtsUser dtsUser = dtsUserFeignClent.getDtsUserById(maintUserId);
        DtsUser dtsUser = maintRptMapper.maintFindDtsUserByUserId(maintUserId);
        if (!ObjectUtils.isEmpty(dtsUser)) {
            assignSite(dtsUser.getCommunityId(), maintRpt);
        }
        return true;
    }

    /**
     * 到达报修点
     *
     * @param id
     * @return
     */
    @Override
    public boolean arrive(Long id) {
        MaintRpt maintRpt = getById(id);
        maintRpt.setArriveTime(new Date());
        maintRpt.setStatus((byte) 4);
        return updateById(maintRpt);
    }

    /**
     * 维修完成
     *
     * @param id
     * @return
     */
    @Override
    public boolean repairDone(Long id, Long userId) {
        MaintRpt maintRpt = getById(id);
        maintRpt.setMaintTime(new Date());
        maintRpt.setStatus((byte) 5);
        redisTemplate.boundHashOps("maintTasks").delete(userId);
        return updateById(maintRpt);
    }

    @Override
    public boolean servCmt(Map<String, String> parm) {
        Long id = Long.valueOf(parm.get("id"));
        MaintRpt maintRpt = getById(id);
        maintRpt.setStatus((byte) 6);
        maintRpt.setServiceComment(Byte.valueOf(parm.get("serviceComment")));
        maintRpt.setTechnicalEvaluation(Byte.valueOf(parm.get("technicalEvaluation")));
        maintRpt.setSpeedEvaluation(Byte.valueOf(parm.get("speedEvaluation")));
        maintRpt.setEvaluationDescr(parm.get("evaluationDescr"));
        redisTemplate.boundHashOps("maintQueue").delete(maintRpt.getUserId());
        return updateById(maintRpt);
    }


    /**
     * 查询用户是否有派单
     *
     * @param userId
     * @return
     */
    @Override
    public int maintCout(Long userId) {
        return count(new LambdaQueryWrapper<MaintRpt>()
                .eq(MaintRpt::getRepairUserId, userId)
                .in(MaintRpt::getStatus, Arrays.asList(2))
        );
    }

    /***
     * 查看用户报案信息
     * @param page
     * @param userId
     * @param maintRealName
     * @param maintMobile
     * @return
     */
    @Override
    public PageResult<MaintRptDto> findUserReport(Page<MaintRptDto> page, Long userId, String maintRealName, String maintMobile) {
        List<MaintRptDto> maintRptDtos = maintRptMapper.listUserReport(new PageAdapter(page), userId, maintRealName, maintMobile);
        Long total = maintRptMapper.countUserReport(userId, maintRealName, maintMobile);
        PageResult<MaintRptDto> pageResult = new PageResult<>(total, maintRptDtos);
        return pageResult;
    }

    /***
     * 查看维修师傅信息
     * @param id
     * @return
     */
    @Override
    public MaintUserDto findMaintUserInfo(Long id) {
        MaintUserDto maintUserDto = maintRptMapper.findMaintUserInfo(id);
        Double d = 0.0;
        if (!ObjectUtils.isEmpty(maintUserDto) && maintUserDto.getMaintUserId() != null) {
            Long maintUserId = maintUserDto.getMaintUserId();
            MaintUserFraction userFraction = maintRptMapper.countOrder(maintUserId);
            if (!ObjectUtils.isEmpty(userFraction) && userFraction.getSum1() != null) {
                int total = userFraction.getSum1() + userFraction.getSum2() + userFraction.getSum3();
                double score = total / (userFraction.getCount() * 3);
                d = MyMathUtil.dFormat(score);
            } else {
                d = 5.0;
            }
        }
        maintUserDto.setFraction(d);
        return maintUserDto;
    }

    @Override
    public List<DispatchHistoryDto> dispatchHistory(Long id) {
        return maintRptMapper.findDispatchHistory(id);
    }

    /***
     * 查看用户报案信息列表
     * @param page
     * @param query
     * @return
     */
    @Override
    public PageResult<MaintRptDto> findMaintenanceList(Page<MaintRptDto> page, MaintQuery query) {
        List<MaintRptDto> list = maintRptMapper.findMaintenanceList(new PageAdapter(page), query);
        Long total = maintRptMapper.findMaintenanceCount(query);
        return new PageResult<MaintRptDto>(total, list);
    }

    /***
     * 查询附近站点
     * @param longitude
     * @param latitude
     * @param keywords
     * @return
     */
    @Override
    public List<CommunityDto> findNearbyCommunity(Page page, Double longitude, Double latitude, String keywords) {
        List<SysCommunity> communityList = maintRptMapper.findCommunityList(null, keywords, new PageAdapter(page));
        List<CommunityDto> list = null;
        if (1 == 1) {
            list = new ArrayList<>();
            for (SysCommunity datum : communityList) {
                CommunityDto communityDto = new CommunityDto();
                BeanUtils.copyProperties(datum, communityDto);
                list.add(communityDto);
            }
            if (longitude != null && latitude != null) {
                Collections.sort(list, new Comparator<CommunityDto>() {
                    @Override
                    public int compare(CommunityDto o1, CommunityDto o2) {
                        double distance1 = CaculateDistance.LantitudeLongitudeDist(longitude, latitude, o1.getLongitude(), o1.getLatitude());
                        double distance2 = CaculateDistance.LantitudeLongitudeDist(longitude, latitude, o2.getLongitude(), o2.getLatitude());
                        double d = distance1 - distance2;
                        return (int) d;
                    }
                });
                for (CommunityDto communityDto : list) {
                    double distance = CaculateDistance.LantitudeLongitudeDist(longitude, latitude, communityDto.getLongitude(),
                            communityDto.getLatitude()) / 1000;
                    communityDto.setDistance(MyMathUtil.dFormat(distance));
                }
            }
        }
        return list;
    }

    @Override
    public Long findNearbyCommunityCount(String keywords) {
        return maintRptMapper.findNearbyCommunityCount(keywords);
    }

    @Override
    public List<MaintRptDto> selectMaintenanceList() {

        //List<BigDataInfoFask> bigDataInfoFasks = guaranteeSiteMapper.selectAllFakeData();
        //List<BigDataInfo> list = new ArrayList<>();
        //for (BigDataInfoFask bigDataInfoFask : bigDataInfoFasks) {
        //    BigDataInfo bigDataInfo = new BigDataInfo();
        //    if (bigDataInfoFask.getIsFake() == 1) {
        //        bigDataInfo.setTote(bigDataInfoFask.getTote());
        //        bigDataInfo.setFailures(bigDataInfoFask.getFailures());
        //
        //        bigDataInfo.setHitchhe(bigDataInfoFask.getHitchhe());
        //        bigDataInfo.setProhibithe(bigDataInfoFask.getProhibithe());
        //        bigDataInfo.setBackNum(bigDataInfoFask.getBackNum());
        //        bigDataInfo.setRecoveryNum(bigDataInfoFask.getRecoveryNum());
        //        bigDataInfo.setFailureNum(bigDataInfoFask.getFailureNum());
        //        bigDataInfo.setFailurePercentage(bigDataInfoFask.getFailurePercentage());
        //        bigDataInfo.setRecoveryPercentage(bigDataInfoFask.getRecoveryPercentage());
        //        bigDataInfo.setBackLv(bigDataInfoFask.getBackLv());
        //
        //        bigDataInfo.setFailure(bigDataInfoFask.getFailure());
        //        bigDataInfo.setRecovery(bigDataInfoFask.getRecovery());
        //        bigDataInfo.setFailureRate(bigDataInfoFask.getFailureRate());
        //        bigDataInfo.setRecoveryRate(bigDataInfoFask.getRecoveryRate());
        //        bigDataInfo.setRentTheusers(bigDataInfoFask.getRentTheusers());
        //
        //        list.add(bigDataInfo);
        //        return list;
        //    }
        //}
        return maintRptMapper.selectRepairUserinfo();
    }

    /**
     * 维修任务
     *
     * @param maintRpt
     */
    private void assignMaint(MaintRpt maintRpt) {
        List<SysCommunity> communitys = getCommunity();
        boolean flag = true;
        for (SysCommunity community : communitys) {
            //根据经纬度计算距离m
            double distance = CaculateDistance.LantitudeLongitudeDist(community.getLongitude(), community.getLatitude(),
                    maintRpt.getLongitude(), maintRpt.getLatitude());

            //站点辐射距离km
            Double radDistance = community.getRadDistance();
            if (distance >= radDistance*1000) {
                continue;
            }
            //站点分配维修任务
            assignSite(community.getId(), maintRpt);
            flag = false;
            break;
        }
        if (flag) {
            //存进队列
            saveQueue(maintRpt);
        }

    }

    /**
     * 站点分配维修任务
     *
     * @param id
     * @param maintRpt
     */
    private void assignSite(Long id, MaintRpt maintRpt) {
        log.info("【分配给该站点维修任务id : {}, 维修信息: {}】： ", id, JSON.toJSONString(maintRpt, true));
        //根据站点id 查询站点下得维修人员
        List<DtsUser> dtsUser = maintRptMapper.getDtsUser(id, maintRpt.getId());
        if (CollectionUtils.isEmpty(dtsUser)) {
            //存入维修队列
            saveQueue(maintRpt);
            return;
        }
        DtsUser maintUser = isOrder(dtsUser, maintRpt.getId());
        if (ObjectUtils.isEmpty(maintUser)) {
            //存入维修队列
            saveQueue(maintRpt);
            return;
        }
        Integer count = redisUtils.getCacheObject("communityMaintRpt:" + id);
        if (count == null) {
            count = 1;
            redisUtils.setCacheObject("communityMaintRpt:" + id, 1, 1, TimeUnit.DAYS);
        } else {
            redisUtils.setCacheObject("communityMaintRpt:" + id, count + 1, 1, TimeUnit.DAYS);
        }
        if (count > dtsUser.size()) {
            redisUtils.deleteObject("communityMaintRpt:" + id);
            //存入维修队列
            saveQueue(maintRpt);
            return;
        }
        maintRpt.setStatus((byte) 2);
        maintRpt.setRepairUserId(maintUser.getId());
        updateById(maintRpt);
        //下发维修任务
        try {
            lock.lock();
            MaintTasks maintTasks = new MaintTasks();
            maintTasks.setMaintUserId(maintUser.getId());
            maintTasks.setMaintRptId(maintRpt.getId());
            maintTasks.setStatus((byte) 1);
            rabbitTemplate.convertAndSend("DISTRI_ORDER_EXCHANGE", "distriOrder",
                    JSON.toJSONString(new AssignSiteParam(id, maintRpt)));
            redisTemplate.boundHashOps("maintTasks").put(maintUser.getId(), 1);
            maintTasksMapper.insert(maintTasks);
            String emergencyContact = maintUser.getEmergencyContact();
            if (!StringUtils.isEmpty(emergencyContact)) {
                send(new DispatchDto(maintRpt, maintUser.getRealName()), emergencyContact);
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * 发送邮件
     *
     * @param dispatchDto
     * @param address
     */
    public void send(DispatchDto dispatchDto, String address) {
        try {
            mailService.sendHtmlMail(address, dispatchDto, "notice");
        } catch (MessagingException e) {
            log.error("维修派单发送短息失败：{}", JSON.toJSONString(e));
        }
    }

    /**
     * 监听未接单超时未结单
     *
     * @param message 接收的信息
     */
    @RabbitListener(queues = "DEAD_LETTER_QUEUE_DISTRI_ORDER")
    public void receiveB(Message message) {
        String msg = new String(message.getBody());
        // 记录日志
        log.info("【监听未接单超时未结单】：{}", msg);
        AssignSiteParam siteParam = JSONObject.parseObject(msg, AssignSiteParam.class);
        if (siteParam != null && siteParam.getMaintRpt().getStatus().intValue() == 2) {
            assignSite(siteParam.getCommunityId(), siteParam.getMaintRpt());
        }
    }


    /**
     * 获取可接单用户
     *
     * @param dtsUser
     * @return
     */
    public DtsUser isOrder(List<DtsUser> dtsUser, Long maintRptId) {
        for (DtsUser user : dtsUser) {
            MaintUserFraction userFraction = maintRptMapper.countOrder(user.getId());
            if (userFraction != null  && userFraction.getCount() > 0){
                int total = userFraction.getSum1() + userFraction.getSum2() + userFraction.getSum3();
                double score = total / (userFraction.getCount() * 3);
                user.setFraction(score);
            }

        }
        //Collections.sort(dtsUser, Comparator.comparingDouble(DtsUser::getFraction));
        Collections.sort(dtsUser, new Comparator<DtsUser>() {
            @Override
            public int compare(DtsUser o1, DtsUser o2) {
                int  f1 = (int)o1.getFraction() *100;
                int  f2 = (int)o2.getFraction() *100;
                return f2 - f1;
            }
        });

        Object hasKey = redisUtils.getCacheObject("maint:userQuery:" + maintRptId);
        if (hasKey == null) {
            redisUtils.setCacheObject("maint:userQuery:" + maintRptId, 1, 1, TimeUnit.DAYS);
            for (DtsUser user : dtsUser) {
                //redisTemplate.opsForList().leftPush(":" + maintRptId, JSON.toJSONString(user));
                redisTemplate.boundListOps("maintUserQuery:" + maintRptId).leftPush(JSON.toJSONString(user));
            }
        }
        //Object userJson = redisTemplate.opsForList().rightPop("maint:userQuery:" + maintRptId);
        Object userJson = redisTemplate.boundListOps("maintUserQuery:" + maintRptId).rightPop();
        if (userJson != null) {
            DtsUser dtsUser1 = JSONObject.parseObject(userJson.toString(), DtsUser.class);
            return dtsUser1;
        }
        return null;


    }

   /* public DtsUser isOrder(List<DtsUser> dtsUser, Long maintRptId) {
        Object hasKey = redisUtils.getCacheObject("maint:userQuery:" + maintRptId);
        if (hasKey == null) {
            redisUtils.setCacheObject("maint:userQuery:" + maintRptId, 1, 1, TimeUnit.DAYS);
            for (DtsUser user : dtsUser) {
                //redisTemplate.opsForList().leftPush(":" + maintRptId, JSON.toJSONString(user));
                redisTemplate.boundListOps("maintUserQuery:" + maintRptId).leftPush(JSON.toJSONString(user));
            }
        }
        //Object userJson = redisTemplate.opsForList().rightPop("maint:userQuery:" + maintRptId);
        Object userJson = redisTemplate.boundListOps("maintUserQuery:" + maintRptId).rightPop();
        if (userJson != null) {
            DtsUser dtsUser1 = JSONObject.parseObject(userJson.toString(), DtsUser.class);
            return dtsUser1;
        }
        return null;
    }*/
//    public DtsUser isOrder(List<DtsUser> dtsUser, Long maintRptId) {
//        boolean flag = false;
//        for (DtsUser user : dtsUser) {
//            int count = maintTasksMapper.findIsOrder(user.getId(), maintRptId);
//            if (count > 0) {
//                continue;
//            }
//            flag = true;
//            Object userJson = redisTemplate.boundHashOps("maintUserNum").get(user.getId());
//            if (ObjectUtils.isEmpty(userJson)) {
//                redisTemplate.boundHashOps("maintUserNum").put(user.getId(), JSON.toJSONString(new MaintUserNumDto(user, 0L)));
//            }
//        }
//        if (!flag) {
//            return null;
//        }
//        List<String> userJsons = redisTemplate.boundHashOps("maintUserNum").values();
//        if (!CollectionUtils.isEmpty(userJsons)) {
//            List<MaintUserNumDto> users = new ArrayList<>(userJsons.size());
//            for (String userJson : userJsons) {
//                MaintUserNumDto userNumDto = JSONObject.parseObject(userJson, MaintUserNumDto.class);
//                users.add(userNumDto);
//            }
//            MaintUserNumDto userNumDto = users.get(0);
//            Long min = users.get(0).getNum();
//            for (int i = 0; i < users.size(); i++) {
//                if (min > users.get(i).getNum()) {
//                    min = users.get(i).getNum();
//                    userNumDto = users.get(i);
//                }
//            }
//            Long num = userNumDto.getNum();
//            redisTemplate.boundHashOps("maintUserNum").put(userNumDto.getMaintUser().getId(), JSON.toJSONString(new MaintUserNumDto(userNumDto.getMaintUser(), num + 1)));
//            return userNumDto.getMaintUser();
//        }
//        return null;
//    }


    /***
     * 存入维修队列
     * @param maintRpt
     */
    private void saveQueue(MaintRpt maintRpt) {
        log.info("【存入维修队列】");
        maintRptMapper.updateRepairUser(maintRpt.getId());
        maintRpt.setStatus((byte) 1);
        String jsonString = JSON.toJSONString(maintRpt);
        redisTemplate.boundHashOps("maintQueue").put(maintRpt.getUserId(), jsonString);
        //用户报案30分钟没人接单自动取消订单
        rabbitTemplate.convertAndSend("HALL_ORDER_EXCHANGE", "hallOrder", JSON.toJSONString(maintRpt.getId()));
    }


    /**
     * 获取维修站点
     *
     * @return
     */
    private List<SysCommunity> getCommunity() {
        List<SysCommunity> communityList = maintRptMapper.findCommunityList((byte) 1, "", null);
        return communityList;
    }


    /**
     * 限制用户提交参数
     *
     * @param userId
     */
    private void reportCheckd(Long userId) {
        int count = maintRptMapper.reportCheckd(userId);
        if (count > 0)
            throw new ApiException("已有预约维修服务订单, 无需重复报修!");
    }
}
